﻿/// <summary>Initializes the AJAX Control Framework on the client-side.</summary>
(function() {
	
	window.AjaxControlFramework = {};
	window.$ajax = AjaxControlFramework;
	
	// Execute the onLoad handlers once the DOM has finished loading.
	// Insanely mad props go out to Matthias Miller, Dean Edwards, and John Resig
	// whose efforts and juicy brains engineered the following code that detects 
	// when the DOM has finished loading in the 4 major rendering engines.
	
	var fireOnLoad = function() {
		if ($ajax.IsLoaded === true)
		{
			return;
		}
		else
		{
			$ajax.IsLoaded = true;
		}

		
		for (var clientID in $ajax.RegisteredControls)
		{
			$ajax.RegisteredControls[clientID].FireEvent("OnLoad");
		}
	};
	
	
	if (document.addEventListener)
	{
		// Gecko, Presto, and WebKit implementation.
		document.addEventListener("DOMContentLoaded", function() {
			document.removeEventListener("DOMContentLoaded", arguments.callee, false);
			fireOnLoad();
		}, false);


		// window.onload fallback.
		document.addEventListener("load", function() {
			document.removeEventListener("load", arguments.callee, false);
			fireOnLoad();
		}, false);
	}
	else if (document.attachEvent)
	{
		document.attachEvent("onreadystatechange", function() {
			if (document.readyState === "complete")
			{
				document.detachEvent("onreadystatechange", arguments.callee);
				fireOnLoad();
			}
		});


		/*
		// A fallback to window.onload, that will always work
			window.attachEvent( "onload", jQuery.ready );


		function doScrollCheck() {
			if ( jQuery.isReady ) {
				return;
			}

			try {
				// If IE is used, use the trick by Diego Perini
				// http://javascript.nwbox.com/IEContentLoaded/
				document.documentElement.doScroll("left");
			} catch(e) {
				setTimeout( doScrollCheck, 1 );
				return;
			}

			// and execute any waiting functions
			jQuery.ready();
		}
		*/

	}
	
	
	// Just in case, attach the load handler to the window.onload event.
	if (typeof window.onload === "function")
	{
		var onloadFunc = window.onload;
		
		window.onload = function() {
			onloadFunc();
			fireOnLoad();
		};
	}
	else
	{
		window.onload = fireOnLoad;
	}


	var fireOnUnload = function() {
		if ($ajax.RegisteredControls)
		{
			for (var clientID in $ajax.RegisteredControls)
			{
				$ajax.RegisteredControls[clientID].Dispose();
			}
		}
	};


	// Handle the window.onbeforeunload event. Within the event handler, call Dispose on all registered AJAX Controls.
	if (typeof window.onunload === "function")
	{
		var onUnloadFunc = window.onunload;
		
		window.onunload = function() {
			onUnloadFunc();
			fireOnUnload();
		};
	}
	else
	{
		window.onunload = fireOnUnload;
	}


	$ajax.ControlsCollection = function() {
		var rootNode = new $ajax.ControlsCollectionNode(null, null, null); // Private member.


		this.Add = function(control) {
			if (!control || !control.ClientID) { return; }
			
			var idParts = control.ClientID().split($ajax.ClientIDSeparator);
			if (idParts.length === 0) { return; }

			var currentNode = rootNode;
			var currentLevel = 0;
			var found = false;

			while (currentLevel < idParts.length)
			{
				found = false;

				for (idPart in currentNode.ChildNodes)
				{
					if (idPart === idParts[currentLevel])
					{
						found = true;
						currentNode = currentNode.ChildNodes[idPart];

						currentLevel++;
						break; // Continue on deeper into the new child node.
					}
				}


				if (!found)
				{
					var newNode = new $ajax.ControlsCollectionNode(idParts[currentLevel], null, currentNode);
					currentNode.ChildNodes[idParts[currentLevel]] = newNode;

					currentNode = newNode;
					currentLevel++;
				}
			}

			if (currentNode != null)
			{
				currentNode.Control = control;
			}
		};


		this.Remove = function(control) {
			if (!control || !control.ClientID) { return; }
			
			var idParts = control.ClientID().split($ajax.ClientIDSeparator);
			if (idParts.length === 0) { return; }

			var currentNode = rootNode;
			var parentNode = rootNode;
			var currentLevel = 0;
			var currentIDPart = null;
			var found = false;

			while (currentLevel < idParts.length)
			{
				found = false;

				for (idPart in currentNode.ChildNodes)
				{
					if (idPart === idParts[currentLevel])
					{
						found = true;
						parentNode = currentNode;
						currentNode = currentNode.ChildNodes[idPart];
						currentIDPart = idPart;

						currentLevel++;
						break; // Continue on deeper into the new child node.
					}
				}


				if (!found)
				{
					currentNode = null;
					break;
				}
			}

			if (currentNode != null && currentIDPart != null)
			{
				delete parentNode.ChildNodes[currentIDPart];
			}
		};



		$ajax.ControlsCollection.DefaultContinueToNextLevelMethod = function(currentNode) { };
		$ajax.ControlsCollection.DefaultFoundMethod = function(currentNode) { return (currentNode.ID === this.CurrentIDPart); };
		$ajax.ControlsCollection.DefaultReturnMethod = function(currentNode) { return (this.CurrentLevel === this.IDParts.length - 1); };

		$ajax.ControlsCollection.SearchContext = function() {
			this.FoundNode = function(currentNode, currentLevel, searchResults) { return true; }; // Determines if a control of interest has been found.
			this.OnFoundNode = function(currentNode, currentLevel, searchResults) { return false; }; // Performs extra custom operations against the context required to further on the search.
			this.OnNotFoundNode = function(currentNode, currentLevel, searchResults) { return false; }; // Performs extra custom operations against the context required to further on the search.
			this.ReturnResults = function(currentNode, currentLevel, searchResults) { return true; }; // Determines if the search loop should stop and return what it's found so far.
		};


		this.FindNodes = function(context, currentNode) {
			if (!context) { return; }
			if (!currentNode) { currentNode = rootNode; }

			return this.FindNodes_Recursive(context, currentNode, -1, []);
		};


		this.FindNodes_Recursive = function(context, currentNode, currentLevel, searchResults) {
			if (!context || !currentNode) { return; }
			context.CurrentLevel = currentLevel;


			if (context.FoundNode(currentNode, currentLevel, searchResults) === true)
			{
				context.OnFoundNode(currentNode, currentLevel, searchResults);
				if (context.Done) { return; }

				if (!context.Continue)
				{
					searchResults.push(currentNode);

					if (context.ReturnResults(currentNode, currentLevel, searchResults))
					{
						context.Done = true;
						return searchResults;
					}
				}
				else
				{
					delete context.Continue;
				}
			}
			/*else
			{
				context.OnNotFoundNode(currentNode, currentLevel, searchResults);
			}*/


			if (!context.Done)
			{
				for (idPart in currentNode.ChildNodes)
				{
					searchResults.concat(this.FindNodes_Recursive(context, currentNode.ChildNodes[idPart], currentLevel + 1, searchResults)); // Child nodes remain, drill down deeper into the tree.

					if (context.Done)
					{
						return searchResults; // Don't process any further, return to the previous level of the tree.
					}
				}
			}

			return searchResults; // No more child nodes left, return to the previous level of the tree.
		};


		this.FindControlByID = function(clientID) {
			var searchContext = new $ajax.ControlsCollection.SearchContext();
			searchContext.IDParts = clientID.split($ajax.ClientIDSeparator);

			searchContext.FoundNode = function(currentNode, currentLevel, searchResults) {
				return (currentLevel >= 0 && (currentNode.ID === null || (currentNode.ID === this.IDParts[currentLevel] && currentLevel === this.IDParts.length - 1)));
			};

			searchContext.OnNotFoundNode = function(currentNode, currentLevel, searchResults) {
				if (currentLevel >= 0) { this.Done = true; }
			};

			searchContext.ReturnResults = function(currentNode, currentLevel, searchResults) {
				return (searchResults.length >= 1);
			};

			return this.FindNodes(searchContext);
		};


		this.FindControlsByType = function(controlType) {
			
		};


		this.Clear = function() {
			rootNode = null;
		};
	};


	$ajax.ControlsCollectionNode = function(id, control, parentNode) {
		this.ID = id;
		this.Control = control;
		this.ParentNode = parentNode;
		this.ChildNodes = {};
	};


	$ajax.EventDelegate = function() {
		var handlerId = 0;
		var handlers = {};


		this.AddHandler = function(handler, isInternal) {
			if (!handler) { return; }

			if (isInternal === true)
			{
				this.internal = true;
			}

			handlers[handlerId++] = handler;
		};


		this.RemoveHandler = function(handler) {
			for (var handlerId in handlers)
			{
				if (handlers[handlerId] == handler) { delete handlers[handlerId]; }
			}
		};


		this.ClearHandlers = function() {
			handlers = [];
		};


		this.Fire = function(target, params) {
			for (var handlerId in handlers)
			{
				if (handlers[handlerId]) { handlers[handlerId].call(target, params); }
			}
		};
	};


	$ajax.HandlerQueueState = {
		Inactive: 0,
		Executing: 1,
		Halted: 2,
		Waiting: 3
	};

	window.$qs = $ajax.HandlerQueueState;


	$ajax.HandlerQueue = function() {
		var _handlers = [];
		var _state = $qs.Inactive;


		this.CurrentRequest = null;

	
		/// <summary>Adds the specified handler to the end of the queue.</summary>
		/// <param name="handler">The handler to be added to the queue.</param>
		this.Add = function(handler) {
			if (typeof handler !== "function") { return; }
		
			_handlers.push(handler);
		};
	
	
		/// <summary>Removes the specified handler from the queue, of an integer is specified, the handler at that index. If a handler is not specified, then the top-most handler is removed.</summary>
		/// <param name="handler">The handler function to be removed from the queue, or the zero-based index of the handler to be removed.</param>
		/// <returns>The handler that was removed.</returns>
		this.Remove = function(handler) {
			if (handler == null)
			{
				return _handlers.pop();
			}
			else if (typeof handler === "function")
			{
				var handlerIndex = 0;
			
				for (var index = 0; index < _handlers.length; index++)
				{
					if (handler === _handlers[index])
					{
						return _handlers.splice(index, 1);
					}
				}
			}
			else if (typeof handler === "number" && handler > -1)
			{
				return _handlers.slice(handler, 1);
			}
		};
	
	
		/// <summary>Removes all of the handlers from the queue.</summary>
		/// <returns>An array of all the handlers that were removed.</returns>
		this.Clear = function(abortRunningHandlers) {
			if (abortRunningHandlers === true)
			{
				this.Abort();
			}
			else
			{
				this.Halt();
			}

			return _handlers.splice(0);
		};
	
	
		/// <summary>Iterates through each of the handlers in the queue and executes them, one at a time.</summary>
		/// <remarks>If any of the handlers returns a value of $ajax.HandlerQueueState.Halted, then execution of the queue's handlers is halted.</remarks>
		this.Execute = function(continueIfWaiting) {
			switch (_state)
			{
				case $qs.Waiting:
					
					if (continueIfWaiting === true)
					{
						Execute_Internal();
					}

					break;

				case $qs.Halted:
				case $qs.Inactive:

					Execute_Internal();
					break;

				default:

					break;
			}
		};
	
	
		/// <summary>Halts the execution of the queue handlers.</summary>
		/// <remarks>If a handler is currently being executed, then execution will not be halted until it has finished.</remarks>
		this.Halt = function(abortCurrent) {
			if (this.IsExecuting() === true)
			{
				if (abortCurrent === true)
				{
					if (this.CurrentRequest != null)
					{
						this.CurrentRequest.abort();
					}

					if (_handlers.length > 0)
					{
						_handlers.pop();
					}
				}
				
				_handlers.push(function() { return $qs.Halted; });
				Execute_Internal();
			}
		};


		/// <summary>Halts the execution of the queue handlers and aborts the currently executing AJAX request, if there is one.</summary>
		/// <remarks>If a handler is currently being executed, then execution will not be halted until it has finished.</remarks>
		this.Abort = function() {
			this.Halt(true);
		};


		this.AbortCurrent = function() {
			if (this.IsExecuting() === true)
			{
				if (this.CurrentRequest != null)
				{
					this.CurrentRequest.abort();
				}

				if (_handlers.length > 0)
				{
					_handlers.pop();
				}

				Execute_Internal();
			}
		};
	
	
		/// <summary>Returns whether or not the queue is currently executing handlers.</summary>
		/// <returns>True if the queue is currently executing handlers, false otherwise.</returns>
		this.IsExecuting = function() {
			return (_state === $qs.Executing || _state === $qs.Waiting);
		};
	
	
		function Execute_Internal()
		{
			var result;
			var continueExecution = true;
		
			while (continueExecution)
			{
				result = ExecuteNext();
				
				switch (result)
				{
					case $qs.Waiting:
					case $qs.Halted:
					case $qs.Inactive:

						_state = result;
						continueExecution = false;
						break;

					default:
						
						_state = $qs.Executing; // This will be the result of handlers that are being invoked...except for AJAX call invocations make by the AJAX Control Framework which will return a Waiting respose after invoking the asynchronous call.
						break;
				}
			}
		}
	
	
		function ExecuteNext()
		{
			if (_handlers.length > 0)
			{
				_state = $qs.Executing;
				return (_handlers.pop())();
			}
		
			return $qs.Inactive;
		}
	};



	/*
	 Heartbeat Options: 
		Interval (integer) - the interval (in miliseconds) at which a send a pulse to the server-side.

	 */


	$ajax.Heartbeat = function(options) {
		
		var onPulseStarting = new $ajax.EventDelegate();
		var onPulseCompleted = new $ajax.EventDelegate();


		// Setup the options.

		//if (options)
		//{
			//if (
		//}


		this.Init = function() {
			
		};


		this.AddPulseStartingHandler = function(handler) {
			onPulseStarting.AddHandler(handler, false);
		};


		this.AddPulseCompletedHandler = function(handler) {
			onPulseCompleted.AddHandler(handler, false);
		};


		this.RemovePulseStartingHandler = function(handler) {
			
		};
	};

	
	Array.prototype.Serialize = function() {
		if (this.DoNotSerialize !== undefined) { return; } // Opt-out.
		
		var json = [];
		
		for (var i = 0; i < this.length; i++)
		{
			if (this[i] === undefined)
			{
				continue;
			}
			else if (this[i] === null)
			{
				json.push("null");
			}
			else if (this[i].DoNotSerialize !== undefined)
			{
				continue;
			}
			else if (this[i].Serialize !== undefined)
			{
				json.push(this[i].Serialize());
			}
			else
			{
				json.push($serialize(this[i]));
			}
		}
		
		return "[" + json.join(",") + "]";
	};

	
	Function.prototype.Serialize = function() {
		throw new TypeError("Functions cannot be convert to JSON.");
	};
	
	
	String.prototype.Serialize = function() {
		if ($ajax.EscapeCharacters.test(this))
		{
			return "\"" + this.replace($ajax.EscapeCharacters, function(match) {
				var replacement = $ajax.EscapeReplacements[match];

				if (typeof replacement === "string")
				{
					return replacement;
				}
				else
				{
					return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
				}
			}) + "\"";
		}
		else
		{
			return "\"" + this + "\"";
		}
	};
	
	
	Date.prototype.Serialize = function() {
		return '"\\/Date(' + this.getTime() + ')\\/"';
	};

	
	Number.prototype.Serialize = function() {
		return this.toString();
	};
	
	
	Boolean.prototype.Serialize = function() {
		return this.valueOf();
	};


	// serializable - default value is "false".
	Function.prototype.CreateProperty = function(propertyName, defaultValue, serializable) {
		if (!this.prototype.GetSetProperty) { return; }

		eval("this.prototype['" + propertyName.toString() + "'] = function(v) { return this.GetSetProperty('" + propertyName.toString() + "', v, " + (defaultValue !== undefined ? (typeof defaultValue === "function" ? defaultValue.toString() : $ajax.Serialize(defaultValue)) : "undefined") + ", " + (serializable === true ? "true" : "undefined" ) + "); };");
	};


	Function.prototype.CreateProperties = function(properties) {
		if (!this.prototype.GetSetProperty) { return; }

		for (var i = 0; i < properties.length; i++)
		{
			if (properties[i].length === 1)
			{
				this.CreateProperty(properties[i][0]);
			}
			else if (properties[i].length === 2)
			{
				this.CreateProperty(properties[i][0], undefined, properties[i][1]);
			}
			else if (properties[i].length === 3)
			{
				this.CreateProperty(properties[i][0], properties[i][1], properties[i][2]);
			}
		}
	};


	Function.prototype.CreateControl = function (controlName, criteria, searchType) {
		if (!this.prototype.GetSetControl) { return; }

		eval("this.prototype['" + controlName.toString() + "'] = function(v) { if (typeof v === 'object') { return this.GetSetControl('" + controlName.toString() + "', v); } else { return this.GetSetControl('" + controlName.toString() + "', undefined, " + (!criteria ? "'" + controlName.toString() + "'" : $ajax.Serialize(criteria)) + ", " + (searchType != null ? searchType.toString() : "0") + "); } };");
	};


	Function.prototype.CreateControls = function (controls) {
		if (!this.prototype.GetSetControl) { return; }

		for (var i = 0; i < controls.length; i++)
		{
			if (controls[i].length === 1)
			{
				this.CreateControl(controls[i][0]);
			}
			else if (controls[i].length > 1)
			{
				this.CreateControl(controls[i][0], controls[i][1], controls[i][2]);
			}
		}
	};
	
	
	
	/* Global Properties */    
	$ajax.EscapeCharacters = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
	$ajax.EscapeReplacements = { "\"": "\\\"", "\\": "\\\\", "\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r", "\t": "\\t" };
	$ajax.ClientIDSeparator = "_";
	
	$ajax.RegisteredControls = {};
	$ajax.Controls = new $ajax.ControlsCollection();
	$ajax.Queue = new $ajax.HandlerQueue();
	$ajax.Heartbeat = new $ajax.Heartbeat();
	
	$ajax.Defaults = {
		DisplayExceptionWhenOnFailureIsUndefined: true,
		HandlerUrl: "/AjaxControlHandler.ashx" + location.search,
		LoadingPanelClass: "loading",
		LoadingBackgroundClass: "loading_bg",
		LoadingMessage: "Loading. Please wait...",
		LoadingMessageClass: "message",
		LoadingMessageContainerClass: "message_container",
		LoadingImagePath: "",
		LoadingImageAlt: "loading..."
	};
	
	$ajax.Settings = $ajax.Defaults;
	
	$ajax.IsLoaded = false;

	$ajax.SearchType = { ID: 0, CssSelector: 1, AjaxControl: 2, ID_Page: 3, AjaxControl_Page: 4 };
	$ajax.ClientIDMode = { AutoID: 1, Predictable: 2, Static: 3 };
	$ajax.LoadID = 0;
	
	
	
	/* Infrastructure Functions */

	$ajax.ShowLoadingPanel = function(control, message, containerControl, loadingImagePath, loadingImageAlt) {
		var loadingPanelID = control.ClientID() + "_loading";
		var loadingPanel = document.getElementById(loadingPanelID);
		
		if (loadingPanel == null)
		{
			loadingPanel = $ajax.BuildLoadingPanel(loadingPanelID, message, loadingImagePath, loadingImageAlt);

			if (containerControl != null)
			{
				containerControl.appendChild(loadingPanel);
			}
			else
			{
				control.FindStartMarker();
				control.FindEndMarker();

				if (control.endMarker)
				{
					control.endMarker.parentNode.insertBefore(loadingPanel, control.endMarker);
				}
			}
		}
		else
		{
			loadingPanel.style.display = "";
		}
	};


	$ajax.BuildLoadingPanel = function(panelID, message, loadingImagePath, loadingImageAlt) {
		var loadingPanel = document.createElement("div");
		loadingPanel.setAttribute("id", panelID);
		loadingPanel.setAttribute("class", $ajax.Settings.LoadingPanelClass);

		var loadingBackground = document.createElement("div");
		loadingBackground.setAttribute("class", $ajax.Settings.LoadingBackgroundClass);
		
		var loadingMessageContainer = document.createElement("div");
		loadingMessageContainer.setAttribute("class", $ajax.Settings.LoadingMessageContainerClass);

		var loadingMessage = $ajax.BuildLoadingMessage(loadingMessageContainer, message, loadingImagePath, loadingImageAlt);

		loadingPanel.appendChild(loadingBackground);
		loadingPanel.appendChild(loadingMessageContainer);

		return loadingPanel;
	};


	$ajax.BuildLoadingMessage = function(loadingMessageContainer, message, loadingImagePath, loadingImageAlt) {
		var loadingMessage = document.createElement("span");
		loadingMessage.setAttribute("class", $ajax.Settings.LoadingMessageClass);

		if (message != null)
		{
			loadingMessage.innerHTML = message;
		}
		else
		{
			loadingMessage.innerHTML = $ajax.Settings.LoadingMessage;
		}


		var loadingImageSrc = (loadingImagePath != null ? loadingImagePath : $ajax.Settings.LoadingImagePath);
		var loadingImage = null;

		if (loadingImageSrc != null && loadingImageSrc !== "")
		{
			loadingImage = document.createElement("img");

			loadingImage.setAttribute("src", loadingImageSrc);
			loadingImage.setAttribute("alt", (loadingImageAlt != null ? loadingImageAlt : $ajax.Settings.LoadingImageAlt));

			loadingMessageContainer.appendChild(loadingMessage);
		}


		if (loadingImage != null)
		{
			loadingMessageContainer.appendChild(loadingImage);
		}
	};


	$ajax.HideLoadingPanel = function(control) {
		var loadingPanel = document.getElementById(control.ClientID() + "_loading");

		if (loadingPanel != null)
		{
			loadingPanel.style.display = "none";
		}
	};

	
	var handlerId = 0;
	
	$ajax.OnLoad = function(handler) {
		if (!handler.id) { handler.id = handlerId++; }
		if (!$ajax.LoadHandlers) { $ajax.LoadHandlers = {}; }
		
	   $ajax.LoadHandlers[handler.id] = handler;
	};


	$ajax.Register = function(control, loadHandlerName) {
		control.Register();
		
		// Try to attach the Load handler function for the control.
		if (!loadHandlerName || typeof loadHandlerName !== "string") { return; }

		control.AddEventHandler("OnLoad", function() {
			if (typeof window[loadHandlerName] === 'function') 
			{
				window[loadHandlerName].call(this, this);
				delete this.InitialState;
			}
		}, true);
	};


	$ajax.DynamicRegister = function(control, loadHandlerName) {
		$ajax.Register(control, loadHandlerName);
	};


	$ajax.Unregister = function(control) {
		control.Unregister();
	};


	$ajax.LoadControl = function(control, loadChildControls) {
		control.FireEvent("OnLoad");

		if (loadChildControls === true)
		{
			// Load all child AJAX Controls as well.
			var childControls = control.ChildControls();
			for (var childControlID in childControls)
			{
				childControls[childControlID].FireEvent("OnLoad");
			}
		}
	};
	
	
	$ajax.DisplayException = function(exception) {
		if (exception) { alert((exception.__type ? (exception.__type.indexOf(",") > -1 ? exception.__type.substring(0, exception.__type.indexOf(",")) : exception.__type) + "\n" : "") + (exception.Message ? exception.Message + "\n\n" : "") + (exception.StackTrace ? exception.StackTrace : "")); }
		else { alert("An unhandled exception has been thrown."); }
	};
	
	
	$ajax.Serialize = function(obj) {        
		if (obj == null || obj.DoNotSerialize !== undefined || obj.nodeType !== undefined) { return; } // Opt-out and don't serialize DOM elements.
		
		var json = [];

		if (obj.Serialize !== undefined) { return obj.Serialize(); }
		else
		{
			if (typeof obj !== "object") { return "null"; }
			else
			{
				for (var property in obj)
				{
					if (obj[property] === undefined) { continue; }
					
					if (obj[property] === null)
					{
						json.push(property + ':null');
						continue;
					}
					
					if (!obj.propertyIsEnumerable(property) || obj[property].DoNotSerialize !== undefined) { continue; }
					
					if (typeof obj[property] === "function") { continue; }
					
					json.push(property + ':' + $serialize(obj[property]));
				}
			}
		}

		return "{" + json.join(",") + "}";
	};
	window.$serialize = $ajax.Serialize; // Shorthand function.

	
	$ajax.Deserialize = function(str) {
		if (typeof str !== "string")
		{
			return str;
		}
		
		return eval('(' + (str === null || str.length === 0 ? 'null' : str) + ')');
	};
	window.$deserialize = $ajax.Deserialize; // Shorthand function.
	
	
	$ajax.RestoreDefaults = function() {
		$ajax.Settings = $ajax.Defaults;
	};
	
	
	$ajax.CreateType = function(fullTypeName) {
		var typeParts = fullTypeName.split(",", 1);
		if (typeParts.length === 0) { return; }

		var types = typeParts[0].split(".");
		var currentObj = window;
		var typeCreated = false;

		for (var i = 0; i < types.length; i++)
		{
			var type = types[i];

			if (typeof currentObj[type] !== 'object' || currentObj[type] === null)
			{
				currentObj[type] = {};
				typeCreated = true;
			}

			currentObj = currentObj[type];
		}

		return typeCreated;
	};
	window.$type = $ajax.CreateType; // Shorthand function.


	$ajax.GetType = function(fullTypeName) {
		var typeParts = fullTypeName.split(",", 1);
		if (typeParts.length === 0) { return null; }

		var types = typeParts[0].split(".");
		var currentObj = window;
  
		for (var i = 0; i < types.length; i++)
		{
			var type = types [i];

			if ((typeof currentObj[type] !== "object" && typeof currentObj[type] !== "function") || currentObj[type] === null)
			{
				return null;
			}
	
			currentObj = currentObj[type];
		}
  
		return currentObj;
	};
	window.$gettype = $ajax.GetType; // Shorthand function.


	$ajax.GetNewShorthandID = function() {
		var shorthandID = 1;

		while (window["ac" + shorthandID])
		{
			shorthandID++;
		}

		return shorthandID;
	};


	$ajax.InterceptSubmit = function(handler) {
		if (typeof window["__doPostBack"] === "function")
		{
			var standardPostBack = window["__doPostBack"];

			window["__doPostBack"] = function(eventTarget, eventArgument) {
				handler();
				window["__doPostBack"] = standardPostBack;
			};
		}
	};
	
	
	$ajax.RecursiveFindMarker = function(currentNode, markerContent) {
		if (!currentNode) { return undefined; }
		
		do
		{
			if (currentNode.nodeType === 8 && currentNode.nodeValue.indexOf(markerContent) > -1)
			{
				return currentNode;
			}
			else if (currentNode.nodeType === 1)
			{
				var node = $marker.call(this, currentNode.firstChild, markerContent);
				if (node != null) { return node; }
			}
		}
		while(currentNode = currentNode.nextSibling);
		
		return undefined;
	};
	window.$marker = $ajax.RecursiveFindMarker; // Shorthand function.
	
	
	$ajax.InvokeCallback = function(control, options, params) {
		var ajaxRequest = null;
		
		try
		{
			ajaxRequest = new XMLHttpRequest();
		}
		catch (e)
		{
			try
			{
				ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch (e)
			{
				try
				{
					ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
				}
				catch (e)
				{
					// AJAX is not supported in the current browser. Fire the onFailure handler 
					// immediately instead of throwing an Error, keeping to the theme of transparency.
					control.HandleFailure({
						__type: "AJAX not supported.", 
						Message: "The current browser does not provide an implementation for the XMLHttpRequest object."
					}, options);
				}
			}
		}

		
		try
		{
			ajaxRequest.onreadystatechange = function() { 
				if (ajaxRequest.readyState === 4)
				{
					// Check for an Abort response.
					if (ajaxRequest.responseText === "a") { return; }
				
					if (options.Enqueue === true)
					{
						options.Queue.CurrentRequest = null;
					}

					WebForm_ExecuteCallback({ 
						xmlRequest: ajaxRequest,
						eventCallback: function (result, context)
						{
							control.HandleSuccess(result, context.Params, context.Options);

							if (context.Options.Enqueue === true)
							{
								options.Queue.Execute(true);
							}
						},
						errorCallback: function(exception, context)
						{
							control.HandleFailure(exception, context.Params, context.Options);

							if (context.Options.Enqueue === true)
							{
								options.Queue.Execute(true);
							}
						},
						context: { Options: options, Params: params }
					});


					if (options.ShowLoadingPanel === true)
					{
						$ajax.HideLoadingPanel(control);
					}
				}
				else if (ajaxRequest.readyState === 0)
				{
					if (options.Enqueue === true)
					{
						options.Queue.CurrentRequest = null;
						options.Queue.Execute(true);
					}
					
				}
			};
		

			var handlerUrl = control.HandlerUrl();
			ajaxRequest.open("POST", (typeof handlerUrl === "string" && handlerUrl.length > 0 ? handlerUrl : $ajax.Settings.HandlerUrl), true);
		

			if (ajaxRequest.setRequestHeader && !window.FormData)
			{
				ajaxRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			}
		

			if (options.ShowLoadingPanel === true)
			{
				$ajax.ShowLoadingPanel(control, options.LoadingMessage, options.LoadingPanelContainer, options.LoadingImagePath, options.LoadingImageAlt);
			}

			if (options.Enqueue === true)
			{
				options.Queue.CurrentRequest = ajaxRequest;
			}



			// Loop through the array of actual AJAX method parameters being passed to the server-side, and for any parameter 
			// that is a function, execute it and replace the function with the returned value in the "Parameters" array. BAM!
			if (params.Parameters && params.Parameters.length && params.Parameters.length > 0)
			{
				for (var index = 0; index < params.Parameters.length; index++)
				{
					if (typeof params.Parameters[index] === "function")
					{
						params.Parameters[index] = params.Parameters[index].call(control);
					}
				}
			}



			ajaxRequest.send($ajax.BuildCallbackData(control, params));
		}
		catch (e)
		{
			// AJAX is not supported in the current browser. Fire the onFailure handler 
			// immediately instead of throwing an Error, keeping to the theme of transparency.
			control.HandleFailure({
				__type: "AJAX not supported.", 
				Message: "The current browser does not provide an implementation for the XMLHttpRequest object." + (navigator.appVersion.indexOf("MSIE") !== -1 ? " Ensure that the \"Enable native XMLHTTP support\" setting is enabled within Internet Options of Internet Explorer (found under the \"Advanced\" tab in the \"Security\" section)." : "")
			}, options);
		}
	};


	/// <summary>Builds the URL that will be POSTed to as part of the AJAX call.</summary>
	/// <param name="control">The AJAX Control being operated on.</param>
	/// <param name="params">The callback parameters.</param>
	$ajax.BuildCallbackData = function(control, params) {
		var callbackData = control.GenerateCallbackData(params.CallbackMethod === "Load" || params.SkipPostBackData === true);

		if (window.FormData)
		{
			callbackData.append("__AJAXCONTROLID", encodeURIComponent(control.UniqueID()));
			callbackData.append("__AJAXPARAM", encodeURIComponent($serialize(params)));
		}
		else
		{
			callbackData += "__AJAXCONTROLID=" + encodeURIComponent(control.UniqueID()) + "&__AJAXPARAM=" + encodeURIComponent($serialize(params));
		}
		
		return callbackData;
	};
	
	
	$ajax.RecursiveFormElementSearch = function(element, data) {
		var tagName = element.tagName.toLowerCase();
		
		if (tagName === "input")
		{
			var type = element.type;
			var id = element.id;
			
			if ((type === "text" || type === "hidden" || type === "password" || type === "email" || ((type === "checkbox" || type === "radio" ) && element.checked === true)) && id !== "__EVENTVALIDATION" && id !== "__VIEWSTATE" && id !== "__VIEWSTATEENCRYPTED") // Don't include viewstate! BLARRRGGGG!!!
			{
				if (window.FormData)
				{
					data.append(element.name, element.value);
				}
				else
				{
					data.push(element.name + "=" + encodeURIComponent(element.value) + "&");
				}
			}
			else if (type === "file" && window.FormData)
			{
				for (var i = 0, file; file = element.files[i]; ++i)
				{
					data.append(element.name, file);
				}
				
				data.hasFile = true;
			}
		}
		else if (tagName === "select")
		{
			if (element.multiple === true)
			{
				for (var index = 0; index < element.options.length; index++)
				{
					if (element.options[index].selected === true)
					{
						if (window.FormData)
						{
							data.append(element.name, element.options[index].value);
						}
						else
						{
							data.push(element.name + "=" + encodeURIComponent(element.options[index].value) + "&");
						}
					}
				}
			}
			else if (element.selectedIndex !== -1)
			{
				if (window.FormData)
				{
					data.append(element.name, element.options[element.selectedIndex].value);
				}
				else
				{
					data.push(element.name + "=" + encodeURIComponent(element.options[element.selectedIndex].value) + "&");
				}
			}
		}
		else if (tagName === "textarea")
		{
			if (window.FormData)
			{
				data.append(element.name, element.value);
			}
			else
			{
				data.push(element.name + "=" + encodeURIComponent(element.value) + "&");
			}
		}
		
		
		if (element.children && tagName !== "select") // Check for SELECT so OPTIONS aren't iterated. Would be a waste.
		{
			for (var index = 0; index < element.children.length; index++)
			{
				data = $ajax.RecursiveFormElementSearch(element.children[index], data);
			}
		}
		
		
		return data;
	};
	
	
	$ajax.RecursiveCloneNode = function(node) {
		var clonedNode = node.cloneNode(false);
		
		if (node.childNodes)
		{
			for (var index = 0; index < node.childNodes.length; index++)
			{
				clonedNode.appendChild($ajax.RecursiveCloneNode(node.childNodes[index]));
			}
		}
		
		if (clonedNode.nodeName == "SELECT")
		{
			clonedNode.selectedIndex = node.selectedIndex;
		}
		
		return clonedNode;
	};


	$ajax.ForEachControlNode = function(control, iterationHandler, iterationEndHandler) {
		if (iterationHandler)
		{
			control.FindStartMarker();
			control.FindEndMarker();
		
			if (control.startMarker && control.endMarker)
			{
				// Remove all nodes between the start and end markers.
				var currentNode = control.startMarker.nextSibling, nextNode = null;
				
				while (currentNode !== null && (nextNode = currentNode.nextSibling) && nextNode != control.endMarker)
				{
					iterationHandler(control, control.startMarker, control.endMarker, currentNode);
					currentNode = nextNode;
				}


				if (iterationEndHandler)
				{
					iterationEndHandler(control, control.startMarker, control.endMarker);
				}
			}
		}
	};


	$ajax.LoadExtraScripts = function(validators, expandos, startupScripts, control) {
		// Initialize any validators that were rendered during the Update.
		var validatorArr = [];

		if (validators)
		{
			if (!window.Page_Validators) { window.Page_Validators = []; }
			var validatorElement;

			for (var validator in validators)
			{
				if (window[validator] && typeof window[validator].dispose === "function")
				{
					for (var i = 0; i < window.Page_Validators.length; i++)
					{
						if (window.Page_Validators[i].id === validator)
						{
							window.Page_Validators.splice(i, 1);
							break;
						}
					}


					if (window[validator].parentElement)
					{
						window[validator].parentElement.removeChild(window[validator]);
					}
				}

				validatorElement = eval(validators[validator].ArrayDeclaration);

				if (validatorElement)
				{
					window.Page_Validators.push(validatorElement);
					validatorArr.push(validatorElement);

					if (validators[validator].Expandos)
					{
						for (var expando in validators[validator].Expandos)
						{
							validatorElement[expando] = validators[validator].Expandos[expando];
						}
					}
				}
			}
		}


		if (expandos)
		{
			for (var controlID in expandos)
			{
				(window[controlID])[expandos[controlID].Name] = expandos[controlID].Value;
			}
		}


		if (startupScripts)
		{
			control.FindStartMarker();
			control.FindEndMarker();

			for (var i = 0; i < startupScripts.length; i++)
			{
				var scriptElement = null;
				
				if (startupScripts[i].indexOf("<script") === 0)
				{
					var scriptPlaceholder = document.createDocumentFragment();

					var scriptElement = document.createElement("ins");
					scriptElement.innerHTML = startupScripts[i];
				}
				else
				{
					scriptElement = document.createElement("script");
					scriptElement.setAttribute("type", "text/javascript");

					try 
					{
						scriptElement.appendChild(document.createTextNode(startupScripts[i]));      
					}
					catch (e)
					{
						//scriptElement.text = startupScripts[i]; // for IE.
					}
				}

				if (control.endMarker && control.endMarker.parentNode)
				{
					control.endMarker.parentNode.insertBefore(scriptElement, control.endMarker);
					control.endMarker.parentNode.removeChild(scriptElement);
				}
			}
		}


		if (validatorArr.length > 0)
		{
			if (typeof $ajax.ValidatorOnLoad !== "function" && typeof ValidatorOnLoad === "function")
			{
				var validatorOnLoadStr = ValidatorOnLoad.toString().replace("function ValidatorOnLoad() {", "");
				$ajax.ValidatorOnLoad = new Function("Page_Validators", "Page_IsValid", "Page_ValidationActive", validatorOnLoadStr.substring(0, validatorOnLoadStr.length - 1));
			}

			if (typeof $ajax.ValidatorOnLoad === "function")
			{
				$ajax.ValidatorOnLoad(validatorArr, false, false);
			}
		}
	};


	$ajax.GetChildControls = function() {		
		var childControls = {};
		var thisClientID = this.ClientID();

		if ($ajax.RegisteredControls && thisClientID)
		{
			for (var clientID in $ajax.RegisteredControls)
			{
				if (clientID.length > thisClientID.length && clientID.indexOf(thisClientID) === 0)
				{
					childControls[clientID] = $ajax.RegisteredControls[clientID];
				}
			}
		}

		return childControls;
	};


	$ajax.GetParentControl = function() {
		var searchContext = new $ajax.ControlsCollection.SearchContext();
		searchContext.IDParts = this.ClientID().split($ajax.ClientIDSeparator);

		searchContext.FoundNode = function(currentNode, currentLevel, searchResults) {
			return (currentLevel >= 0 && (currentNode.ID === null || (currentNode.ID === this.IDParts[currentLevel] && currentLevel === this.IDParts.length - 1)));
		};

		searchContext.OnNotFoundNode = function(currentNode, currentLevel, searchResults) {
			if (currentLevel >= 0) { this.Done = true; }
		};

		searchContext.ReturnResults = function(currentNode, currentLevel, searchResults) {
			return (searchResults.length >= 1);
		};

		var nodes = $ajax.Controls.FindNodes(searchContext);

		if (nodes && nodes.length > 0)
		{
			var node = nodes[0];
			
			while ((node = node.ParentNode) != null)
			{
				if (node.Control != null) { return node.Control; }
			}
		}

		return null;
	};
	
	
	
	/* AjaxControl Class */
	window.AjaxControl = {};
	
	// Required Parameters: properties (object)
	// Optional Parameters: initialState (object)
	AjaxControl = function(properties, initialState) {
		this.SetProperties(properties, true, true);
		if (initialState) { this.InitialState = initialState; }
	};
	
	
	/* 
	Optional Parameters:
		ReturnHtml (boolean) - 'false' by default,
		AutoUpdateHtml (boolean) - 'false' by default,
		OnSuccess (function),
		OnFailure (function),
		Params (array),
		TrackControlState (boolean) - 'true' by default,
		State (object) - 'null' by default,
		LoadOutOfBand (boolean) - 'false' by default,
		AppendResultTo (element),
		PrependResultTo (element),
		GZipResponse (boolean) - 'false' by default,
		SkipPostBackData (boolean) - 'false' by default,
		ShowLoadingPanel (boolean) - 'false' by default,
		LoadingMessage (string) - 'undefined' by default,
		LoadingPanelContainer (element) - 'undefined' by default,
		LoadingImagePath (string) - 'undefined' by default,
		LoadingImageAlt (string) - 'undefined' by default,
		Enqueue (boolean) - 'undefined' by default,
		Queue (HandlerQueue) - The global HandlerQueue instance by default,
		LoadProperties (object) - 'undefined' by default
	*/


	AjaxControl.prototype = {
		GetType: function() {
			return "$ajax.AjaxControl, $ajax";
		},

		/*
		Required Parameters: *none*
		*/
		Update: function (options) {
			this.$i("Update", {}, options);
		},


		/*
		Required Parameters: controlType (string)
		*/
		Load: function(controlType, options) {
			if (typeof controlType === "object" && !options)
			{
				this.$i("Load", null, controlType);
			}
			else
			{
				this.$i("Load", { ControlType: controlType }, options);
			}
		},


		/* 
		Required Parameters: methodName (string)
		*/
		Invoke: function(methodName, options) {
			this.$i("Invoke", { MethodName: methodName }, options);
		},


		/* 
		Required Parameters: controlType (string), methodName (string)
		*/
		GlobalInvoke: function(controlType, methodName, options) {
			this.$i("GlobalInvoke", { ControlType: controlType, MethodName: methodName }, options);
		},


		AppendTo: function(parent) {
			// tempHtml is a DocumentFragment object.
			if (this.tempHtml != null && parent)
			{
				parent.appendChild(document.createComment(this.ClientID() + "_start"));
				parent.appendChild(this.tempHtml);
				parent.appendChild(document.createComment(this.ClientID() + "_end"));

				delete this.tempHtml;

				if (this.extraScripts)
				{
					$ajax.LoadExtraScripts(this.extraScripts.Validators, this.extraScripts.Expandos, this.extraScripts.StartupScripts, this);
					delete this.extraScripts;
				}

				$ajax.LoadControl(this, true);
			}
			else if (parent)
			{
				this.FindStartMarker();

				parent.appendChild(this.startMarker);
				
				$ajax.ForEachControlNode(this, function(control, startMarker, endMarker, currentNode) {
					parent.appendChild(currentNode);
				}, function(control, startMarker, endMarker) { 
					if (endMarker.previousSibling !== null)
					{
						parent.appendChild(endMarker.previousSibling);
					}
					
					parent.appendChild(endMarker);
				});
			}
		},

		
		PrependTo: function(reference) {
			if (this.tempHtml != null && reference && reference.parentNode != null)
			{
				reference.parentNode.insertBefore(document.createComment(this.ClientID() + "_start"), reference);
				reference.parentNode.insertBefore(this.tempHtml, reference);
				reference.parentNode.insertBefore(document.createComment(this.ClientID() + "_end"), reference);

				delete this.tempHtml;

				if (this.extraScripts)
				{
					$ajax.LoadExtraScripts(this.extraScripts.Validators, this.extraScripts.Expandos, this.extraScripts.StartupScripts, this);
					delete this.extraScripts;
				}

				$ajax.LoadControl(this, true);
			}
			else if (reference && reference.parentNode != null)
			{
				this.FindStartMarker();

				reference.parentNode.insertBefore(this.startMarker, reference);
				
				$ajax.ForEachControlNode(this, function(control, startMarker, endMarker, currentNode) {
					reference.parentNode.insertBefore(currentNode, reference);
				}, function(control, startMarker, endMarker) { 
					if (endMarker.previousSibling !== null)
					{
						reference.parentNode.insertBefore(endMarker.previousSibling, reference);
					}
					
					reference.parentNode.insertBefore(endMarker, reference);
				});
			}
		},


		Remove: function() {
			$ajax.ForEachControlNode(this, function(control, startMarker, endMarker, currentNode) {
				currentNode.parentNode.removeChild(currentNode);
			}, function(control, startMarker, endMarker) { 
				if (endMarker.previousSibling !== null)
				{
					endMarker.parentNode.removeChild(endMarker.previousSibling);
				}

				if (startMarker.parentNode != null)
				{
					startMarker.parentNode.removeChild(endMarker);
					startMarker.parentNode.removeChild(startMarker);
				}
			});
		},


		Dispose: function() {
			this.FireEvent("Disposing");
			this.Remove();


			var childControls = this.ChildControls();
			for (var childControlID in childControls)
			{
				childControls[childControlID].Dispose();
			}


			for (var control in this.Controls)
			{
				delete this.Controls[control];
				delete this[control];
			}

			delete this.Controls;


			// Deleting all of the Properties and Child Controls helps in mitigating memory leaks in your JavaScript.
			for (var property in this.Properties)
			{
				delete this.Properties[property];
				delete this[property];
			}

			delete this.Properties;


			this.FireEvent("Disposed");
			this.RemoveAllEventHandlers();

			delete $ajax.RegisteredControls[this.ClientID()];
		},
		
		
		/// <summary>Replaces the AJAX Control's existing HTML with new HTML.</summary>
		/// <remarks>This function searched the DOM for the HTML comment nodes that mark the start and end of the 
		/// AJAX Control's markup, clears out the existing nodes in between the markers, and replaces it with 
		/// the new HTML provided.</remarks>
		/// <param name="html" optional="false" mayByNull="false" type="String">The new HTML to replace the existing 
		/// HTML of the AJAX Control.</param>
		UpdateHtml: function(html, updateOptions) {
			if (!html || html === null) { return; }

			if (updateOptions == null || updateOptions.DisposeChildren !== false)
			{
				var childControls = this.ChildControls();
				for (var childControlID in childControls)
				{
					childControls[childControlID].Dispose();
				}
			}

			if (updateOptions == null || updateOptions.RemoveEvents !== false) // check if the "RemoveEvents" option is specifically set to "false".
			{
				this.RemoveAllEventHandlers(false, true); // Remove only non-internal event handlers.
			}


			this.Controls = {};

			
			$ajax.ForEachControlNode(this, function(control, startMarker, endMarker, currentNode) {
				currentNode.parentNode.removeChild(currentNode);
			}, function(control, startMarker, endMarker) { 
				if (endMarker.previousSibling !== null)
				{
					endMarker.parentNode.removeChild(endMarker.previousSibling);
				}
				
				// Insert the specified new HTML as a new node before the end marker node.
				var emptyElement = document.createElement("ins");
				emptyElement.innerHTML = html;
				
				

				var placeholder = null;
				
				if (updateOptions != null && updateOptions.Attach === "Prepend")
				{
					placeholder = updateOptions.Target;

					if (placeholder.parentNode != null)
					{
						placeholder.parentNode.insertBefore(startMarker, placeholder);

						for (var index = 0; index < emptyElement.childNodes.length; index++)
						{
							placeholder.parentNode.insertBefore($ajax.RecursiveCloneNode(emptyElement.childNodes[index]), placeholder);
						}

						placeholder.parentNode.insertBefore(endMarker, placeholder);
					}
				}
				else
				{
					if (updateOptions != null && updateOptions.Attach === "Append")
					{
						placeholder = updateOptions.Target;

						placeholder.appendChild(startMarker);
					}
					else
					{
						placeholder = document.createDocumentFragment();
					}


					for (var index = 0; index < emptyElement.childNodes.length; index++)
					{
						placeholder.appendChild($ajax.RecursiveCloneNode(emptyElement.childNodes[index]));
					}


					if (updateOptions != null && updateOptions.Attach === "Append")
					{
						placeholder.appendChild(endMarker);
					}
					else
					{
						endMarker.parentNode.insertBefore(placeholder, endMarker);
					}
				}

				
				$ajax.LoadControl(control, true);
			});
		},
		
		
		FindStartMarker: function() {
			// Check for cached start and end marker nodes.
			if (!this.startMarker || this.startMarker.parentNode === null || this.startMarker.parentNode.nodeType !== 1)
			{
				// Search the DOM for the start marker, starting at the body of the document.
				var startMarker = $marker.call(this, document.body, this.ClientID() + "_start");
				if (startMarker != null)
				{
					this.startMarker = startMarker;
					this.startMarker.DoNotSerialize = true;
				}
			}
		},
		
		
		FindEndMarker: function() { 
			if ((this.startMarker && !this.endMarker) || (this.endMarker && this.endMarker.parentNode === null) || (this.endMarker && this.endMarker.parentNode.nodeType !== 1))
			{
				// Search the DOM, starting after the start marker.
				var endMarker = $marker.call(this, this.startMarker, this.ClientID() + "_end");
				if (endMarker != null)
				{
					this.endMarker = endMarker;
					this.endMarker.DoNotSerialize = true;
				}
			}
		},


		// Required Parameters: criteria (string)
		// Optional Parameters: searchType ($ajax.SearchType)
		FindControl: function(criteria, searchType) {
			if (!searchType || searchType === $ajax.SearchType.ID)
			{
				return this.FindControlByID(criteria);
			}
			else if (searchType == $ajax.SearchType.AjaxControl)
			{
				return this.FindChildControl(criteria);
			}
			else if (searchType === $ajax.SearchType.ID_Page)
			{
				return document.getElementById(criteria);
			}
			else if (searchType === $ajax.SearchType.AjaxControl_Page)
			{
				return this.FindChildAjaxControl_Page(criteria);
			}
			else if (searchType === $ajax.SearchType.Class)
			{
				return this.FindControlByCssSelector(criteria);
			}
		},


		// Required Parameters: id (string)
		FindControlByID: function(id) {
			var control = null;

			if (this.HasDataContainer && this.HasDataContainer() === true && this.ClientIDMode() === $ajax.ClientIDMode.Predictable) 
			{
				var dataBoundControl = false;
				
				if (this.ID().indexOf("ctl") === -1)
				{
					var components = this.UniqueID().split("$");
					var controlID = [];

					for (var index = components.length - 1; index >= 0; index--)
					{
						if (components[index] === this.ID() && index > 0 && components[index - 1].indexOf("ctrl") >= 0)
						{
							dataBoundControl = true;
							control = document.getElementById(this.ClientID() + "_" + id + "_" + components[index - 1].substring(4));
							break;
						}
					}
				}


				if (control === null && dataBoundControl === false)
				{
					control = document.getElementById(this.ClientID() + "_" +  id);
				}
			}
			else
			{
				control = document.getElementById(this.ClientID() + "_" +  id);
			}

			if (control === null)
			{
				// If the control could not be found using normal conventions, do a recursive 
				// search into the DOM to find the control that ends with ("_" + id).

			}

			return control;
		},


		// Required Parameters: css (string)
		FindControlByCssSelector: function(css) {
			// Search recursively between the start and end markers of the AjaxControl for the 


		},


		// Required Paramaters: id (string)
		FindChildControl: function(id) {
			var childControls = this.ChildControls();
			for (var childControlID in childControls)
			{
				if (id === childControls[childControlID].ID())
				{
					return childControls[childControlID];
				}
			}

			return null;
		},


		FindChildAjaxControl_Page: function(id) {
			if ($ajax.RegisteredControls)
			{
				for (var clientID in $ajax.RegisteredControls)
				{
					if (clientID.lastIndexOf(id) === (clientID.length - id.length))
					{
						return $ajax.RegisteredControls[clientID];
					}
				}
			}

			return null;
		},
		
		
		// This method is meant for internal use only. But who am I to stop you?
		GetSetControl: function(name, control, criteria, searchType) {
			if (!this.Controls) { this.Controls = {}; this.Controls.DoNotSerialize = true; }

			if (control !== undefined)
			{
				this.Controls[name] = control;
			}
			else 
			{
				if ((this.Controls[name] === undefined && criteria !== undefined) || (this.Controls[name] === null && searchType === $ajax.SearchType.AjaxControl))
				{
					this.Controls[name] = this.FindControl(criteria, searchType);
				}
				
				return this.Controls[name];
			}
		},
		
		
		GetSetProperty: function(name, value, defaultValue, serializable) {
			if (!this.Properties) { this.Properties = {}; }
			
			if (value !== undefined)
			{ // Setter
				if (serializable !== true && value !== null) { value.DoNotSerialize = true; }
				this.Properties[name] = value;
			}
			else
			{ // Getter
				if (this.Properties[name] === undefined && defaultValue !== undefined)
				{
					if (serializable !== true && defaultValue !== null) { defaultValue.DoNotSerialize = true; }

					if (typeof defaultValue === "function")
					{
						return defaultValue.call(this); // Don't assign the value, we want to re-evaluate property functions every time.
					}
					else
					{
						this.Properties[name] = defaultValue;
					}
				}

				return this.Properties[name];
			}
		},


		// Required: propertyValues (object)
		// Optional: createIfDoesNotExist (boolean), serializableByDefault(boolean)
		SetProperties: function(propertyValues, createIfDoesNotExist, serializableByDefault) {
			if (propertyValues === null) { return; }

			for (propertyName in propertyValues)
			{
				if (typeof this[propertyName] === "function")
				{
					this[propertyName](propertyValues[propertyName]);
				}
				else if (createIfDoesNotExist === true)
				{
					if (this.constructor.prototype[propertyName] === undefined)
					{
						this.constructor.CreateProperty(propertyName, propertyValues[propertyName], serializableByDefault);
					}

					this[propertyName](propertyValues[propertyName]);
				}
			}
		},


		GetEvent: function(name, createIfDoesNotExist) {
			if (!this.Events) { this.Events = {}; this.Events.DoNotSerialize = true; }
			
			if (this.Events[name] === undefined && createIfDoesNotExist === true)
			{
				this.Events[name] = new $ajax.EventDelegate();
			}

			return this.Events[name];
		},


		GetGlobalEvent: function(name, createIfDoesNotExist) {
			if (!this.contstructor.Events) { this.contstructor.Events = {}; this.contstructor.Events.DoNotSerialize = true; }
			
			if (this.contstructor.Events[name] === undefined && createIfDoesNotExist === true)
			{
				this.contstructor.Events[name] = new $ajax.EventDelegate();
			}

			return this.contstructor.Events[name];
		},


		FireEvent: function(name, params, fireGlobalHandlers) {
			/*if (fireGlobalHandlers === undefined || fireGlobalHandlers === true)
			{
				var globalEvent = this.GetGlobalEvent(name);
				if (globalEvent) { globalEvent.Fire(this, params); }
			}*/
			
			var event = this.GetEvent(name);
			if (event) { event.Fire(this, params); }
		},


		FireGlobalEvent: function(name, params) {
			var globalEvent = this.GetGlobalEvent(name);
			 if (globalEvent) { globalEvent.Fire(this, params); }
		},


		AddEventHandler: function(name, handler, isInternal) {
			var event = this.GetEvent(name, true);
			if (event && typeof handler === "function") { event.AddHandler(handler, isInternal); }
		},


		AddGlobalEventHandler: function(name, handler) {
			var event = this.GetGlobalEvent(name, true);
			if (event && typeof handler === "function") { event.AddHandler(handler); }
		},


		RemoveEventHandler: function(name, handler) {
			var event = this.GetEvent(name);

			if (event)
			{
				if (typeof handler === "function") { event.RemoveHandler(handler); }
				else { event.ClearHandlers(); }
			}
		},


		RemoveAllEventHandlers: function(recursive, externalOnly) {
			if (this.Events)
			{
				for (var event in this.Events)
				{
					if (event === "DoNotSerialize")
					{
						continue;
					}

					if (externalOnly === true)
					{
						if (this.Events[event].internal !== true)
						{
							delete this.Events[event];
						}
					}
					else
					{
						delete this.Events[event];
					}
				}

				if (recursive === true)
				{
					var childControls = this.ChildControls();
					for (var childControlID in childControls)
					{
						childControls[childControlID].RemoveAllEventHandlers(recursive, externalOnly);
					}
				}
			}
		},


		Validate: function(validationGroup) {
			
		},


		// This function is a replacement for the WebForm_InitCallback WebForms.js function that generates 
		// the URL of the callback. It has an advantage over WebForm_InitCallback in that it only 
		// collects postback data for elements associated with the specific AJAX Control invoking 
		// the callback, instead of gathering the values of ALL of the postback elements on the entire 
		// Page. This results in less bandwidth usage and shorter execution times. Also, AND MOST 
		// IMPORTANT, it does NOT include any postback data involving ViewState.
		GenerateCallbackData: function(skipPostDataLoad) {
			var callbackData = null;

			if (window.FormData)
			{
				callbackData = new FormData();

				// Gather the postback data for the __EVENTTARGET and EVENTARGUMENT form inputs.
				callbackData.append("__EVENTTARGET", (document.forms[0].__EVENTTARGET && document.forms[0].__EVENTTARGET.value.length > 0 ? document.forms[0].__EVENTTARGET.value : "null")); // This is a hack (pass "null" to the server when __EVENTTARGET is empty) to ensure that the Validate() method isn't invoked against the Page when there is no real __EVENTTARGET.
				callbackData.append("__EVENTARGUMENT", (document.forms[0].__EVENTARGUMENT ? document.forms[0].__EVENTARGUMENT.value : ""));
			}
			else
			{
				callbackData = [];

				// Gather the postback data for the __EVENTTARGET and EVENTARGUMENT form inputs.
				callbackData.push("__EVENTTARGET=" + encodeURIComponent(document.forms[0].__EVENTTARGET && document.forms[0].__EVENTTARGET.value.length > 0 ? document.forms[0].__EVENTTARGET.value : "null") + "&"); // This is a hack (pass "null" to the server when __EVENTTARGET is empty) to ensure that the Validate() method isn't invoked against the Page when there is no real __EVENTTARGET.
				callbackData.push("__EVENTARGUMENT=" + encodeURIComponent(document.forms[0].__EVENTARGUMENT ? document.forms[0].__EVENTARGUMENT.value : "") + "&");
			}
			
			
			if (skipPostDataLoad !== true)
			{
				// Find and cache the <!--ID_start--> and <!--ID_end--> markers surrounding the AJAX Control.
				this.FindStartMarker();
				this.FindEndMarker();
			
				var currentNode = null, nextNode = null;
			
				if (!this.startMarker)
				{
					this.startMarker = document.body.firstChild;
					currentNode = this.startMarker;
				}
				else
				{
					currentNode = this.startMarker.nextSibling;
				}
			
				if (!this.endMarker)
				{
					this.endMarker = document.body.lastChild;
				}
			
			
				if (this.startMarker && this.endMarker)
				{
					// Iterate all nodes between the start and end markers.                
					while (currentNode !== null && currentNode !== this.endMarker && (nextNode = currentNode.nextSibling) && (currentNode.nodeType === 1 || currentNode.nodeType === 3)) // IE doesn't pick up on text nodes. Webkit and Gecko do though.
					{
						if (currentNode.nodeType === 1)
						{
							callbackData = $ajax.RecursiveFormElementSearch(currentNode, callbackData);
						}
					
						currentNode = nextNode;
					}
				}
			}
			
			if (window.FormData)
			{
				return callbackData;
			}
			else
			{
				return callbackData.join("");
			}
		},
		
		
		InitCallback: function(callbackMethodName, required, optional) {
			var params = {};
			if (!optional) { optional = {}; }

			// Append all of the required parameters to the "params" object.
			for (var p in required) { params[p] = required[p]; }

			// Append the valid optional parameters to the "params" object (if they exist).
			if (optional.ReturnHtml) { params.ReturnHtml = optional.ReturnHtml; }
			if (optional.AutoUpdateHtml) { params.AutoUpdateHtml = optional.AutoUpdateHtml; } else if (optional.AutoUpdateHtml === undefined && (callbackMethodName === "Update")) { params.AutoUpdateHtml = true; }
			if (optional.OnSuccess) { params.OnSuccess = optional.OnSuccess; }
			if (optional.OnFailure) { params.OnFailure = optional.OnFailure; }
			if (optional.Params) { params.Parameters = optional.Params; }
			if (optional.State) { params.State = optional.State; }
			if (optional.TrackViewState) { params.TrackViewState = optional.TrackViewState; }
			if (optional.GZipResponse) { params.GZipResponse = optional.GZipResponse; }
			if (optional.SkipPostBackData) { params.SkipPostBackData = optional.SkipPostBackData; } else { optional.SkipPostBackData = false; }
			if (optional.Enqueue) { params.Enqueue = optional.Enqueue; } else if (optional.Enqueue === undefined) { optional.Enqueue = false; }
			if (optional.RemoveHandlersOnUpdate) { params.RemoveHandlersOnUpdate = optional.RemoveHandlersOnUpdate; }

			if (callbackMethodName === "Load") 
			{
				params.OutOfBandID = "ajaxControl" + ($ajax.LoadID++).toString();
				if (optional.LoadProperties) { params.LoadProperties = optional.LoadProperties; }

				// Check to see if the type is defined.
				if ($gettype(params.ControlType) === null)
				{
					params.ControlTypeExists = false;
					params.ShorthandID = $ajax.GetNewShorthandID();
				}
				
				if (!window.Page_ClientValidate)
				{
					params.ValidationScriptLoaded = false;
				}
			}
			else if (this.LoadOutOfBand() === true)
			{
				params.LoadOutOfBand = true;
				params.ControlType = this.GetType();
			}
			else
			{
				params.LoadOutOfBand = false;
			}

			// These parameters are ALWAYS required by the Callback Strategy.
			params.CallbackMethod = callbackMethodName;
			params.Properties = this.Properties;
			params.Path = $ajax.Settings.PageVirtualPath;


			// We're not attaching the "Queue" option to the "params" variable so it doesn't get serialized and stays on the client-side.
			if (!optional.Queue)
			{ 
				optional.Queue = $ajax.Queue;
			}



			function invokeCallback(control, methodName, options, parameters)
			{
				switch (methodName)
				{
					case "Load":

						control.FireEvent("OnLoadStarting");
						break;

					case "Invoke":

						control.FireEvent("OnInvokeStarting");
						break;

					case "GlobalInvoke":

						control.FireEvent("OnGlobalInvokeStarting");
						break;
				}

				if (methodName === "Update" || options.AutoUpdateHtml === true)
				{
					control.FireEvent("OnUpdateStarting");
				}


				// Invoke the callback.
				$ajax.InvokeCallback(control, options, parameters);
			}

			

			// Enqueue the callback invocation if the option is set...
			if (optional.Enqueue === true)
			{
				var self = this;
				optional.Queue.Add(function() {
					invokeCallback(self, callbackMethodName, optional, params);
					return $qs.Waiting;
				});

				optional.Queue.Execute();
			}
			else
			{
				// ...otherwise, just invoke the callback.
				invokeCallback(this, callbackMethodName, optional, params);
			}
		},


		HandleSuccess: function(result, params, options) {
			result = $deserialize(result);

			if (result.RedirectOptions != null)
			{
				var redirectUrl = result.RedirectOptions.RedirectUrl;
				var redirectPause = result.RedirectOptions.PauseBeforeRedirect;
				
				if (redirectUrl != null && redirectUrl.length > 0)
				{
					if (redirectPause > 0)
					{
						var timeoutHandler = function() { 
							window.location = redirectUrl;
						};
						
						setTimeout(timeoutHandler, redirectPause);
						return;
					}
					else
					{
						window.location = redirectUrl;
						return;
					}
				}
			}

			if (!result.State) { result.State = null; }
			
			if ((params.CallbackMethod === "Update" || params.CallbackMethod === "Invoke") && params.AutoUpdateHtml === true && result.Html)
			{
				this.SetProperties(result.Properties);

				var childControls = this.ChildControls();
				for (var childControlID in childControls)
				{
					childControls[childControlID].Dispose();
				}

				this.Controls = {};

				if (params.RemoveHandlersOnUpdate === true)
				{
					this.RemoveAllEventHandlers(false, true); // Remove only non-internal event handlers.
				}


				if (result.ControlScript)
				{
					eval(result.ControlScript);
					delete result.ControlScript;
				}


				var updateOptions = { RemoveEvents: false, DisposeChildren: false };

				if (options.AppendResultTo)
				{
					updateOptions.Attach = "Append";
					updateOptions.Target = options.AppendResultTo;
				}
				else if (options.PrependResultTo)
				{
					updateOptions.Attach = "Prepend";
					updateOptions.Target = options.PrependResultTo;
				}

				this.UpdateHtml(result.Html, updateOptions);


				$ajax.LoadExtraScripts(result.Validators, result.Expandos, result.StartupScripts, this);
			}
			else if (params.CallbackMethod === "Load")
			{
				// "Load" (and "GlobalInvoke") are special in that they can be performed against declared AJAX Controls and non-AJAX Controls alike.
				// When reading this script, keep in mind that any conditional that checks for the existance of a Control instance (i.e. result.Control) 
				// is meant for "Load" calls where the target control is a declared AJAX Control (it has an [AjaxControlOptions] attribute assigned at 
				// the class level).

				// TODO: Verify the above statement...if it's not true, MAKE it true because this is how it should be working.

				if (result.ControlScript && result.ClientID)
				{
					// A ControlScript is only emitted for declared AJAX Controls.
					eval(result.ControlScript);
					delete result.ControlScript;

					result.Control = $ajax.RegisteredControls[result.ClientID];
					delete result.ClientID;
				}

				var placeholder = null;

				if ((result.Control || options.AppendResultTo || options.PrependResultTo) && result.Html)
				{
					// TODO: Refactor this code.
					var tempHtml = document.createElement("ins");
					tempHtml.innerHTML = result.Html;

					placeholder = document.createDocumentFragment();
					for (var index = 0; index < tempHtml.childNodes.length; index++)
					{
						placeholder.appendChild($ajax.RecursiveCloneNode(tempHtml.childNodes[index]));
					}
				}


				if (placeholder != null)
				{
					if (options.AppendResultTo != null)
					{
						if (result.Control)
						{
							result.Control.tempHtml = placeholder;
							result.Control.AppendTo(options.AppendResultTo);

							$ajax.LoadExtraScripts(result.Validators, result.Expandos, result.StartupScripts, this);
						}
						else
						{
							options.AppendResultTo.appendChild(placeholder);

							$ajax.LoadExtraScripts(result.Validators, result.Expandos, result.StartupScripts, this);
						}
					}
					else if (options.PrependResultTo != null)
					{
						if (result.Control)
						{
							result.Control.tempHtml = placeholder;
							result.Control.PrependTo(options.PrependResultTo);

							$ajax.LoadExtraScripts(result.Validators, result.Expandos, result.StartupScripts, this);
						}
						else if (options.PrependResultTo.parentNode != null)
						{
							options.PrependResultTo.parentNode.insertBefore(placeholder, options.PrependResultTo);

							$ajax.LoadExtraScripts(result.Validators, result.Expandos, result.StartupScripts, this);
						}
					}
					else if (result.Control)
					{
						result.Control.tempHtml = placeholder;

						// In the case that both the "AppendResultTo" and "PrependResultTo" options are not provided, 
						// we will need to hold on to the extra scripts that were rendered as part of the callback 
						// result so that we can call the "$ajax.LoadExtraScripts" function 
						// automatically when the "AppendTo" or "PrependTo" functions are called against the 
						// resulting Control. We have to call "LoadExtraScripts" then because the function requires 
						// that the Control's elements be present in the DOM, as all of the extra scripts that will be 
						// invoked will be targeting them.
						result.Control.extraScripts = { Validators: result.Validators, Expandos: result.Expandos, StartupScripts: result.StartupScripts };
					}
				}
			}
			else if (params.CallbackMethod === "GlobalInvoke")
			{
				// GlobalInvoke isn't being handled yet!!  AHHHHH!!
			}
			else
			{
				this.SetProperties(result.Properties);


				if (result.ControlScript)
				{
					eval(result.ControlScript);
					delete result.ControlScript;
				}
			}


			function fireAfterEvents(params, result)
			{
				switch (params.CallbackMethod)
				{
					case "Load":
						
						if (result.Control)
						{
							result.Control.FireEvent("OnLoadCompleted");
						}

						this.FireEvent("OnLoadCompleted");
						break;
						
					case "Invoke":

						this.FireEvent("OnInvokeCompleted");
						break;

					case "GlobalInvoke":

						this.FireEvent("OnGlobalInvokeCompleted");
						break;
				}

				if (params.CallbackMethod === "Update" || (params.AutoUpdateHtml === true && result.Html))
				{
					this.FireEvent("OnUpdateCompleted");
				}
			}


			var self = this;
				
			if (options.OnSuccess)
			{
				setTimeout(function() {
					options.OnSuccess.call(self, result, options);
					fireAfterEvents.call(self, params, result); // Call it this way. No need for another set of closures.
					delete result.Control; // Remove circular reference manually.
				}, 1);
			}
			else
			{
				fireAfterEvents.call(self, params, result); // Call it this way. No need for another set of closures.
			}


			// TODO: Decide whether or not to call the "after" event handlers within "HandleFailure".
		},


		HandleFailure: function(exception, params, options) {
			exception = $deserialize(exception);
			var self = this;
			
			if (options && options.OnFailure) 
			{
				setTimeout(function() {
					options.OnFailure.call(self, exception, options);
				}, 1);
			}
			else if ($ajax.Settings.DisplayExceptionWhenOnFailureIsUndefined === true)
			{
				$ajax.DisplayException(exception);
			}
		},


		Register: function() {
			$ajax.RegisteredControls[this.ClientID()] = this;
			$ajax.Controls.Add(this);
		},


		Unregister: function() {
			$ajax.Controls.Remove(this);
			delete $ajax.RegisteredControls[this.ClientID()];
		}
	};

	AjaxControl.CreateProperties([["HandlerUrl", true], ["ChildControls", $ajax.GetChildControls, false], ["ParentControl", $ajax.GetParentControl, false]]);
	
	
	AjaxControl.prototype.$c = AjaxControl.prototype.GetSetControl;
	AjaxControl.prototype.$p = AjaxControl.prototype.GetSetProperty;
	AjaxControl.prototype.$i = AjaxControl.prototype.InitCallback;

})();